热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

上级|上文_Flutter中的InheritedWidgetInheritedModel

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Flutter中的InheritedWidgetInheritedModel相关的知识,希望对你有一定的参考价值。Flu

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Flutter中的InheritedWidgetInheritedModel相关的知识,希望对你有一定的参考价值。


Flutter中有四种widget

StatelessWidget
StatefullWidget
RenderObjectWidget
InheritedWidget


其中StatelessWidget和StatefulWidget是最常见到的,从状态管理角度的分类。RenderObjectWidget是所有需要渲染的Widget的基类。

至于最后一个InheritedWidget,许多初学者不一定了解,但是在一些稍微复杂的项目中是必须要用到的,所以本文介绍一下InheritedWidget的用法


InheritedWidget



To obtain the nearest instance of a particular type of inherited widget from a build context, use BuildContext.inheritFromWidgetOfExactType.
Inherited widgets, when referenced in this way, will cause the consumer to rebuild when the inherited widget itself changes state.



通常情况下,子widget无法单独感知父widget的变化,当父state变化时,通过其build重建所有子widget;

InheritedWidget可以避免这种全局创建,实现局部的子widget更新:
子widget通过BuildContext.inheritFromWidgetOfExactType从buildContext中获取并监听指定类型的父InheritedWidget,并跟随其重建而rebuild

 

如上图,点击C按钮,State变化后,A的Text可以单独刷新,B不受到影响



代码演示

接下来通过代码对比一下使用或不使用InheritedWidget的区别:

 点击+,后上面的0变化,中间的文字部分不变化。

传统实现

点击按钮state变化后,widgetA、B、C都会rebuild

class TopPage extends StatelessWidget
@override
Widget build(BuildContext context)
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Demo'),
),
body: HomePage(),
),
);

class HomePage extends StatefulWidget
@override
_HomePageState createState() => _HomePageState();
class _HomePageState extends State
int _counter = 0;
void _incrementCounter()
setState(()
_counter++;
);

@override
Widget build(BuildContext context)
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
WidgetA(_counter),
WidgetB(),
WidgetC(_incrementCounter),
],
),
);

class WidgetA extends StatelessWidget
final int counter;
WidgetA(this.counter);
@override
Widget build(BuildContext context)
return Center(
child: Text(
'$counter',
style: Theme.of(context).textTheme.display1,
),
);

class WidgetB extends StatelessWidget
@override
Widget build(BuildContext context)
return Text('I am a widget that will not be rebuilt.');

class WidgetC extends StatelessWidget
final void Function() incrementCounter;
WidgetC(this.incrementCounter);
@override
Widget build(BuildContext context)
return RaisedButton(
onPressed: ()
incrementCounter();
,
child: Icon(Icons.add),
);


使用androidStudio的Flutter Performance可以看到widgetA、B、C都参与了rebuild

 

使用InheritedWidget实现

class TopPage extends StatelessWidget
@override
Widget build(BuildContext context)
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(
child: Scaffold(
appBar: AppBar(
title: Text('InheritedWidget Demo'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
WidgetA(),
WidgetB(),
WidgetC(),
],
),
),
),
);

class _MyInheritedWidget extends InheritedWidget
_MyInheritedWidget(
Key key,
@required Widget child,
@required this.data,
) : super(key: key, child: child);
final HomePageState data;
@override
bool updateShouldNotify(_MyInheritedWidget oldWidget)
return true;

class HomePage extends StatefulWidget
HomePage(
Key key,
this.child,
) : super(key: key);
final Widget child;
@override
HomePageState createState() => HomePageState();
static HomePageState of(BuildContext context, bool rebuild = true)
if (rebuild)
return (context.inheritFromWidgetOfExactType(_MyInheritedWidget) as _MyInheritedWidget).data;

return (context.ancestorWidgetOfExactType(_MyInheritedWidget) as _MyInheritedWidget).data;
// or
// return (context.ancestorInheritedElementForWidgetOfExactType(_MyInheritedWidget).widget as _MyInheritedWidget).data;

class HomePageState extends State
int counter = 0;
void _incrementCounter()
setState(()
counter++;
);

@override
Widget build(BuildContext context)
return _MyInheritedWidget(
data: this,
child: widget.child,
);

class WidgetA extends StatelessWidget
@override
Widget build(BuildContext context)
final HomePageState state = HomePage.of(context);
return Center(
child: Text(
'$state.counter',
style: Theme.of(context).textTheme.display1,
),
);

class WidgetB extends StatelessWidget
@override
Widget build(BuildContext context)
return Text('I am a widget that will not be rebuilt.');

class WidgetC extends StatelessWidget
@override
Widget build(BuildContext context)
final HomePageState state = HomePage.of(context, rebuild: false);
return RaisedButton(
onPressed: ()
state._incrementCounter();
,
child: Icon(Icons.add),
);

 可以看到state变化时,widgetB、C都没有rebuild

关键代码说明

针对InheritedWidget版本中的关键类进行说明

WidgetA、WidgetC

传统版本中WidgetA、C通过构造函数传入父级的state以及回调
InheritedWidget版本中,可以通过如下静态方法获取

final HomePageState state = HomePage.of(context); // WidgetA
final HomePageState state = HomePage.of(context, rebuild: false);  // WidgetC

WidgetC是一个Button需要通过state获取回调方法,但不需要跟随state变化而刷新,所以rebuild指定false

接下来详细看一下获取state的静态方法 HomePage.of

HomePage

static HomePageState of(BuildContext context, bool rebuild = true)
if (rebuild)
return (context.inheritFromWidgetOfExactType(_MyInheritedWidget) as _MyInheritedWidget).data;

return (context.ancestorWidgetOfExactType(_MyInheritedWidget) as _MyInheritedWidget).data;
// or
// return (context.ancestorInheritedElementForWidgetOfExactType(_MyInheritedWidget).widget as _MyInheritedWidget).data;


HomePage.of用来通过buildContext,找到最近的_MyInheritedWidget。然后就可以同_MyInheritedWidget获取其持有的state。

获取上级Widget的几个关键方法如下:


methoddescription
inheritFromWidgetOfExactType获取最近的给定类型的上级Widget,该widget必须是InheritedWidget的子类,并向上级widget注册传入的context,当上级widget改变时,这个context持有的widget会rebuild以便从该widget获得新的值。这就是child向InheritedWidget注册的方法。
inheritFromWidgetOfExactType仅仅用来获取最近的给定类型的上级Widget,不会因为上级Widget的改变而rebuild
ancestorInheritedElementForWidgetOfExactType功能与inheritFromWidgetOfExactType一样,但是只会寻找InheritedWidget的子类,所以可以以O(1)的复杂度查找上级Widget

因此,widgetA随着父widget的变化而rebuild,widgetB并没有rebuild

class _MyInheritedWidget extends InheritedWidget
_MyInheritedWidget(
Key key,
@required Widget child,
@required this.data,
) : super(key: key, child: child);
final HomePageState data;
@override
bool updateShouldNotify(_MyInheritedWidget oldWidget)
return true;


继承自InheritedWidget,所以子Widget可以通过inheritFromWidgetOfExactType获取。

updateShouldNotify控制是否需要子widget感受其变化,如果返回true,则通过inheritFromWidgetOfExactType注册的子widget跟随其变化rebuild

子widget最终目的是要获取共享的父级state,所以这里通过data属性持有了state。

那再来看一下这个HomePageState

HomePageState

@override
Widget build(BuildContext context)
return _MyInheritedWidget(
data: this,
child: widget.child,
);


此处_MyInheritedWidget的使用是关键。

传统写法中,build中直接创建widgetA、B、C并返回,因此每当state变化时,会重新创建子widget并rebuild;

InheritedWidget版本中,HomePage保持父widget(TopPage)的children,当state变化时widgetA、B、C不会重建,而是重新传入给_MyInheritedWidget,重建的只有_MyInheritedWidget

TopPage

class TopPage extends StatelessWidget
@override
Widget build(BuildContext context)
・・・
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ 
WidgetA(), // 子widget的创建移动到这里
WidgetB(),
WidgetC(),
],
),
・・・


根据上文的说明,为了避免子widget的反复创建和rebuild,将widgetA、B、C的实例化移动到这里
InheritedModel

上面的例子中我们通过自定义了rebuild参数来指定子Widget是否参与rebuild,实际上也可以使用InheritedModel完成此需求

InheritedModel继承自InheritedWidget,可以通过字符串key(aspect)来指定特定子widget进行rebuild。

简单看一下InheritedModel版本与InheritedWidget版本在实现上的不同

@override
HomePageState createState() => HomePageState();
static HomePageState of(BuildContext context, String aspect)
return InheritedModel.inheritFrom<_MyInheritedWidget>(context, aspect: aspect).data;



使用 InheritedModel.inheritFrom获取widget

class _MyInheritedWidget extends InheritedModel
&#64;override
bool updateShouldNotifyDependent(_MyInheritedWidget old, Set aspects)
return aspects.contains(&#39;A&#39;); // 当aspect包晗“A”时&#xff0c;通知其rebuild



继承InheritedModel&#xff0c;重写updateShouldNotifyDependent

class WidgetA extends StatelessWidget
&#64;override
Widget build(BuildContext context)
final HomePageState state &#61; HomePage.of(context, &#39;A&#39;); // 注册aspect为“A“
class WidgetC extends StatelessWidget
&#64;override
Widget build(BuildContext context)
final HomePageState state &#61; HomePage.of(context, &#39;C&#39;); // 注册aspect为“C”


如上&#xff0c;因为注册的key(aspect)不同&#xff0c;只有widgetA会受到rebuild的通知


更局部的刷新

如果widgetA是下面这样&#xff0c;我们希望能进一步控制其子widget的局部刷新

class WidgetA extends StatelessWidget
&#64;override
Widget build(BuildContext context)
final HomePageState state &#61; HomePage.of(context);
return Column(
children: [
Center(
child: Text(
&#39;$state.counter&#39;,
style: Theme.of(context).textTheme.display1,
),
),
Text("AAAAA"), // 此处不需rebuild
],
);


如果彻底理解了BuildContext和InheritedWidget的注册机制&#xff0c;是可以很容易实现的&#xff1a;

return Column(
children: [
Center(
child: Builder(builder: (context)
final HomePageState state &#61; HomePage.of(context);
return Text(
&#39;$state.counter&#39;,
style: Theme.of(context).textTheme.display1,
);
),
),
Text("AAAAA"),
],
);

通过Builder来创建一个匿名类widget&#xff0c;然后将HomePage.of移到其内部。此时InheritedWidget中注册的context不再是widgetA而是这个匿名类widget&#xff0c;因此可以实现widgetA的局部刷新


不使用InheritedWidget

我想通过上文的介绍大家应该能够想到&#xff0c;如果子widget仅仅想访问父级state&#xff08;不通过构造函数传参的方式&#xff09;&#xff0c;但没有监听其变化的需要&#xff0c;可以不使用InheritedWidget&#xff1a;

class TopPage extends StatelessWidget
&#64;override
Widget build(BuildContext context)
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text(&#39;Demo&#39;),
),
body: HomePage(),
),
);

class HomePage extends StatefulWidget
HomePageState state; // 持有state供子类获取
&#64;override
HomePageState createState()
state &#61; HomePageState();
return state;

class HomePageState extends State
int counter &#61; 0; // 去掉private
void incrementCounter() // 去掉private
setState(()
counter&#43;&#43;;
);

&#64;override
Widget build(BuildContext context)
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
WidgetA(),
WidgetB(),
WidgetC(),
],
),
);

class WidgetA extends StatelessWidget
&#64;override
Widget build(BuildContext context)
final HomePage widget &#61; context.ancestorWidgetOfExactType(HomePage); // 获取state
final HomePageState state &#61; widget?.state;
return Center(
child: Text(
&#39;$state &#61;&#61; null ? 0 : state.counter&#39;,
style: Theme.of(context).textTheme.display1,
),
);

class WidgetB extends StatelessWidget
&#64;override
Widget build(BuildContext context)
return Text(&#39;I am a widget that will not be rebuilt.&#39;);

class WidgetC extends StatelessWidget
&#64;override
Widget build(BuildContext context)
final HomePage widget &#61; context.ancestorWidgetOfExactType(HomePage);
final HomePageState state &#61; widget?.state;
return RaisedButton(
onPressed: ()
state?.incrementCounter();
,
child: Icon(Icons.add),
);

通过ancestorWidgetOfExactType寻找指定类型的widget&#xff0c;然后获取其state使用&#xff0c;当然这个遍历是O(n)的&#xff0c;性能比InheritedWidget版本要差


最后

Flutter中很多组件都是基于InheritedWidget实现的&#xff0c;例如Scoped Model、BLoC(Business Logic of component)等&#xff0c;想要掌握这些高级特性的使用先从了解InheritedWidget开始吧

代码&#xff1a;
https://github.com/vitaviva/flutter_inherited_widget_sample/tree/master/flutter_inherited_widgethttps://github.com/vitaviva/flutter_inherited_widget_sample/tree/master/flutter_inherited_widget


推荐阅读
  • 第一步:PyQt4Designer设计程序界面该部分设计类同VisvalStudio内的设计,改下各部件的objectName!设计 ... [详细]
  • 使用Flutternewintegration_test进行示例集成测试?回答首先在dev下的p ... [详细]
  • 当我在doWork方法中运行代码时,通过单击button1,进度条按预期工作.但是,当我从其他方法(即btn2,btn3)将列表传递给doWork方法时,进度条在启动后会跳转到10 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • Week04面向对象设计与继承学习总结及作业要求
    本文总结了Week04面向对象设计与继承的重要知识点,包括对象、类、封装性、静态属性、静态方法、重载、继承和多态等。同时,还介绍了私有构造函数在类外部无法被调用、static不能访问非静态属性以及该类实例可以共享类里的static属性等内容。此外,还提到了作业要求,包括讲述一个在网上商城购物或在班级博客进行学习的故事,并使用Markdown的加粗标记和语句块标记标注关键名词和动词。最后,还提到了参考资料中关于UML类图如何绘制的范例。 ... [详细]
  • 本文介绍了贝叶斯垃圾邮件分类的机器学习代码,代码来源于https://www.cnblogs.com/huangyc/p/10327209.html,并对代码进行了简介。朴素贝叶斯分类器训练函数包括求p(Ci)和基于词汇表的p(w|Ci)。 ... [详细]
  • Flutter 布局(四) Baseline、FractionallySizedBox、IntrinsicHeight、IntrinsicWidth详解
    本文主要介绍Flutter布局中的Baseline、FractionallySizedBox、IntrinsicHeight、IntrinsicWidth四种控件,详细介绍了其布局 ... [详细]
  • css div中文字位置_超赞的 CSS 阴影技巧与细节
    本文的题目是CSS阴影技巧与细节。CSS阴影,却不一定是box-shadow与filter:drop-shadow,为啥?因为使用其他属性 ... [详细]
  • Flutter第六章(BottomNavigationBar ,AppBar,TabBar ,TabController 以及案例)
    版权声明:本文为作者原创书籍。转载请注明作者和出处,未经授权,严禁私自转载,侵权必究!!!情感语录:如果你想得到从未拥有过的东西,那么你必须去做从未做过的事 ... [详细]
author-avatar
沧桑的悟空2502859857
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有